/*
 * generator.c
 *
 *  Created on: Oct 28, 2019
 *      Author: Standa
 *		Note: TIM16 is the counter reserved for memory to GPIO DMA1 CH4 transfer triggered by its update event.
 */

#include "generator.h"
#include "sin128.h"
#include "error.h"
#include "gpio.h"
#include "dma.h"
#include "tim.h"
#include "utility.h"
#include "qfplib.h"


#define GEN_FMIN_HZ		1
#define GEN_FMAX_HZ		1000


#define DACBITCNT			5					//Number of R2R DAC bits
#define DACRES				(2<<(DACBITCNT-1))	//Resolution of R2R DAC
#define DACGPIOBITSTART		3					//DAC parallel output port starts at PA3 up to PA7


static uint8_t WaveBuf[SIN128_CNT];
static uint8_t Initialized = 0, Prepared = 0, Running = 0;
static float DesiredFreq_Hz = 0, ActualFreq_Hz = 0;


/*
 * Sets output voltage to zero volts with reference to negative probe electrode voltage (meaning half of MCU supply voltage which is approx 3.3 V)
 */
static void SetOutputToRelativeZero(void){
	GPIOA->ODR = (uint8_t)(((2<<(DACBITCNT+DACGPIOBITSTART-1)) >> 1) - 1);
}


/*
 * Generates data buffer for DMA to GPIO port transfer
 */
void GenerateWaveBuf(void){
	for(uint16_t i = 0; i<SIN128_CNT; i++){
		WaveBuf[i] = (uint8_t)(qfp_float2uint( qfp_fmul(qfp_fmul(0.5f, qfp_fadd(1, Sin128[i])), ((2<<(DACBITCNT+DACGPIOBITSTART-1))-1))) );	//Generate integer sin value between 0 and 255 (8bits) and drop 3 bits to accomodate for 5bit ADC (actually not need to drop three bits as GPIO starts at PA3 up to PA7)
	}
}


/*
 * Initialize generator module, used typically during boot only
 */
void gen_Init(void){
	SetOutputToRelativeZero();
	Initialized = 1;
}


/*
 * Prepares all the configurations and calculations (might be lengthy call)
 */
err_Td gen_Prepare(float freq_Hz){
	LL_RCC_ClocksTypeDef Clk;

	if(freq_Hz<GEN_FMIN_HZ || freq_Hz>GEN_FMAX_HZ) return err_Td_Range;

	SetOutputToRelativeZero();
	GenerateWaveBuf();
	LL_RCC_GetSystemClocksFreq(&Clk);

	//Configure output update timer
	for(int Presc=1; Presc<0xFFFF; Presc++){											//Iterate to find out optimal prescaler
		uint32_t PeriodCnt = qfp_float2uint(qfp_fmul(qfp_fdiv(1, qfp_fmul( freq_Hz, UT_SIZEOFARRAY(WaveBuf) ) ), qfp_fdiv( Clk.SYSCLK_Frequency, Presc ) ) );		//Calculate how high the cnt register gets during single RefreshPeriod (1 / ( freq_Hz * UT_SIZEOFARRAY(WaveBuf)))
		if( PeriodCnt <= UINT16_MAX ){													//If it is less or equal to maximum 16bit register value optimal value of prescaler has been found
			LL_TIM_SetCounter(TIM16, 0);												//Restart the counter
			LL_TIM_SetPrescaler(TIM16, Presc-1);										//Set actual prescaler
			LL_TIM_SetAutoReload(TIM16, PeriodCnt-1);									//Set counter period
			LL_TIM_ClearFlag_UPDATE(TIM16);												//Clear update flag which might be set during configuration

			ActualFreq_Hz = qfp_fdiv( Clk.SYSCLK_Frequency, qfp_fmul( qfp_fmul(Presc, PeriodCnt), UT_SIZEOFARRAY(WaveBuf)) );
			DesiredFreq_Hz = freq_Hz;
			break;
		}
	}

	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4);
	LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_4, (uint32_t)(WaveBuf), (uint32_t)(&(GPIOA->ODR)), LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_4, UT_SIZEOFARRAY(WaveBuf));
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_4);
	LL_TIM_EnableDMAReq_UPDATE(TIM16);

	Prepared = 1;
	return err_Td_Ok;
}


/*
 * Start the signal generation if generator is prepared (gen_Prepare);
 */
err_Td gen_Start(void){
	if(!Prepared || !Initialized) return err_Td_Init;
	else if(Running) return err_Td_Busy;
	Running = 1;
	LL_TIM_EnableCounter(TIM16);														//Enable timer which triggers DMA transfer Sine to GPIO port
	LL_TIM_GenerateEvent_UPDATE(TIM16);
	return err_Td_Ok;
}


/*
 * Stop the signal generation and set the output to relative zero voltage
 */
void gen_Stop(void){
	LL_TIM_DisableCounter(TIM16);								//Stop the timer
	LL_TIM_DisableDMAReq_UPDATE(TIM16);							//Disable DMA
	SetOutputToRelativeZero();
	Running = 0;
	Prepared = 0;
	DesiredFreq_Hz = 0;
	ActualFreq_Hz = 0;
}


/*
 * Returns the state of generator (0-disabled, 1-active)
 */
uint8_t gen_GetState(void){
	return Running;
}

/*
 * Gets actual frequency of excitation (for higher frequencies might not be exactly the one which is desired)
 */
float gen_GetActualFreq(void){
	return ActualFreq_Hz;
}
